#include "YUVItem.h"
#include <QSGGeometry>
#include <QQuickWindow>
#include <QSGGeometryNode>
#include <QSGTexture>
#include <QSGTextureMaterial>
#include <QSGMaterialShader>
#include <QTimer>
#include <QUrl>
#include <QFile>
#include <QByteArray>
#include "tool.h"


class YUVShader : public QSGMaterialShader
{
public:
    YUVShader()
    {
        auto vs =  getResPath(qmlPath + QStringLiteral("yuv.vert.qsb"));
        auto fs =  getResPath(qmlPath + QStringLiteral("yuv.frag.qsb"));

        setShaderFileName(VertexStage, vs);
        setShaderFileName(FragmentStage, fs);
    }
    virtual bool updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
    virtual void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
};


class YUVMaterial : public QSGMaterial
{
public:
    YUVMaterial()
    {
        setFlag(Blending);
    }
    ~YUVMaterial()
    {
        delete mTextureY;
        delete mTextureU;
        delete mTextureV;
    }
    QSGTexture *mTextureY = nullptr;
    QSGTexture *mTextureU = nullptr;
    QSGTexture *mTextureV = nullptr;
public:
    virtual QSGMaterialType *type() const override
    {
        static QSGMaterialType t;
        return &t;
    }
    virtual QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override
    {
        return new YUVShader;
    }
    virtual int compare(const QSGMaterial *o) const override
    {
        Q_ASSERT(o && type() == o->type());
        const auto *other = static_cast<const YUVMaterial *>(o);
        if(other == this )
        {
            return 0;
        }
        if (!mTextureY || !other->mTextureY)
        {
            return mTextureY ? 1 : -1;
        }
        if (int diff = mTextureY->comparisonKey() - other->mTextureY->comparisonKey())
        {
            return diff;
        }
        if (int diff = mTextureU->comparisonKey() - other->mTextureU->comparisonKey())
        {
            return diff;
        }
        if (int diff = mTextureV->comparisonKey() - other->mTextureV->comparisonKey())
        {
            return diff;
        }
        return 0;
    }
};

bool  YUVShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
    bool changed = false;
    QByteArray *buf = state.uniformData();
    Q_ASSERT(buf->size() >= 68);
    if (state.isMatrixDirty())
    {
        const auto &m = state.combinedMatrix();
        memcpy(buf->data(), m.constData(), 64);
        changed = true;
    }
    if (state.isOpacityDirty())
    {
        const float opacity = state.opacity();
        memcpy(buf->data() + 64, &opacity, 4);
        changed = true;
    }
    return changed;
}
void YUVShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
    (void) state;
    (void)binding;

    auto material = static_cast<YUVMaterial *>(newMaterial);

    if (binding == 1) {
        //reference from qt6.2.2 opensource
        material->mTextureY->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
        *texture = material->mTextureY;
    } else if (binding == 2) {
        material->mTextureU->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
        *texture = material->mTextureU;
    } else if (binding == 3) {
        material->mTextureV->commitTextureOperations(state.rhi(), state.resourceUpdateBatch());
        *texture = material->mTextureV;
    }
}

class YUVNode : public QSGGeometryNode
{
public:
    YUVNode(const QImage &y, const QImage &u, const QImage &v, QQuickWindow *pWin)
    {
        //        QImage imageY(w, h, QImage::Format_Grayscale8);
        //        QImage imageUV(w/2, h/2, QImage::Format_Grayscale8);

        auto textureY = pWin->createTextureFromImage(y);
        textureY->setFiltering(QSGTexture::Nearest);
        textureY->setHorizontalWrapMode(QSGTexture::Repeat);
        textureY->setVerticalWrapMode(QSGTexture::Repeat);

        auto textureU = pWin->createTextureFromImage(u);
        textureU->setFiltering(QSGTexture::Nearest);
        textureU->setHorizontalWrapMode(QSGTexture::Repeat);
        textureU->setVerticalWrapMode(QSGTexture::Repeat);

        auto textureV = pWin->createTextureFromImage(v);
        textureV->setFiltering(QSGTexture::Nearest);
        textureV->setHorizontalWrapMode(QSGTexture::Repeat);
        textureV->setVerticalWrapMode(QSGTexture::Repeat);

        mMaterial = new YUVMaterial;
        mMaterial->mTextureY = textureY;
        mMaterial->mTextureU = textureU;
        mMaterial->mTextureV = textureV;
        setMaterial(mMaterial);
        setFlag(OwnsMaterial);

        mGeo = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
        QSGGeometry::updateTexturedRectGeometry(mGeo, QRect(), QRect());
        setGeometry(mGeo);
        setFlag(OwnsGeometry);
    }
    void setRect(const QRectF &bounds)
    {
        QSGGeometry::updateTexturedRectGeometry(mGeo, bounds, QRectF(0, 0, 1, 1));
        markDirty(QSGNode::DirtyGeometry);
    }
    void setYUV(const QImage &y, const QImage &u, const QImage &v, QQuickWindow *pWin)
    {
        auto textureY = pWin->createTextureFromImage(y);
        textureY->setFiltering(QSGTexture::Nearest);
        textureY->setHorizontalWrapMode(QSGTexture::Repeat);
        textureY->setVerticalWrapMode(QSGTexture::Repeat);

        auto textureU = pWin->createTextureFromImage(u);
        textureU->setFiltering(QSGTexture::Nearest);
        textureU->setHorizontalWrapMode(QSGTexture::Repeat);
        textureU->setVerticalWrapMode(QSGTexture::Repeat);

        auto textureV = pWin->createTextureFromImage(v);
        textureV->setFiltering(QSGTexture::Nearest);
        textureV->setHorizontalWrapMode(QSGTexture::Repeat);
        textureV->setVerticalWrapMode(QSGTexture::Repeat);

        delete mMaterial->mTextureY;
        delete mMaterial->mTextureU;
        delete mMaterial->mTextureV;

        mMaterial->mTextureY = textureY;
        mMaterial->mTextureU = textureU;
        mMaterial->mTextureV = textureV;
        markDirty(QSGNode::DirtyMaterial);
    }
private:
    QSGGeometry *mGeo;
    YUVMaterial *mMaterial;
};



YUVItem::YUVItem(QQuickItem* parent)
    : Super(parent)
{
    setFlag(Super::ItemHasContents);

    connect(this, &QQuickItem::xChanged, this, [this](){
        mGeometryChanged = true;
        update();
    });
    connect(this, &QQuickItem::yChanged, this, [this](){
        mGeometryChanged = true;
        update();
    });
    connect(this, &QQuickItem::widthChanged, this, [this](){
        mGeometryChanged = true;
        update();
    });
    connect(this, &QQuickItem::heightChanged, this, [this](){
        mGeometryChanged = true;
        update();
    });


    mTimer = new QTimer(this);
    connect(mTimer, &QTimer::timeout, this, &YUVItem::onTimeout);

    connect(this, &YUVItem::sourceChanged, this,  &YUVItem::onSourceChanged);
}

YUVItem::~YUVItem()
{

}

QSGNode* YUVItem::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* data)
{
    (void)data;
    auto node = static_cast<YUVNode*>(oldNode);
    if (!node) {
        node = new YUVNode(mImageY, mImageU, mImageV, window());
    }
    if (mGeometryChanged)
    {
        node->setRect(boundingRect());
        mGeometryChanged = false;
    }
    if (mYUVChanged) {
        node->setYUV(mImageY, mImageU, mImageV, window());
        mYUVChanged = false;

    }
    return node;

}

void YUVItem::onTimeout()
{
    if (mYUVChanged)
    {
        return;
    }
    if (mDataOffset >= mData.size())
    {
        return;
    }
    if (mDataW * mDataH <=0)
    {
        return;
    }
    int frameSize = mDataW * mDataH + (mDataW * mDataH)/2;
    if (mDataOffset + frameSize >= mData.size())
    {
        return;
    }
    auto frameData = mData.mid(mDataOffset, frameSize);
    mDataY = frameData.left(mDataW * mDataH);
    mDataU = frameData.mid(mDataW * mDataH, mDataW * mDataH/4);
    mDataV = frameData.right(mDataW * mDataH / 4);
    mImageY = QImage((const uchar *)mDataY.constData(), mDataW, mDataH, QImage::Format_Grayscale8);
    mImageU = QImage((const uchar *)mDataU.constData(), mDataW/2, mDataH/2, QImage::Format_Grayscale8);
    mImageV = QImage((const uchar *)mDataV.constData(), mDataW/2, mDataH/2, QImage::Format_Grayscale8);

    mDataOffset += frameSize;
    mYUVChanged = true;
    update();
}

void YUVItem::onSourceChanged()
{
    auto src = getResPath(source().toString());
    QFile file(src);
    if(!file.open(QFile::ReadOnly))
    {
        qWarning() << "open file failed" << file.errorString();
        return;
    }
    mTimer->stop();
    mData = file.readAll();
    file.close();
    mDataW = 352;
    mDataH = 288;
    mTimer->start(1000 / 10);
}
